03-010. 결측치 처리
결측치 처리 - Missing Value Handling
결측치(Missing Value) 처리는 전처리에서 가장 자주 다루는 문제이고 분석에서도 매우 중요한 문제다. 결측치 때문에 분석 결과를 잘못 해석하거나 통계 분석에서 가설 검정을 잘못하는 실수를 하게 된다. 좋은 모델을 만드는 데 방해가 되기도 한다.
**ℹ️ 알아두기: 모델(Model)은 데이터를 이용해 패턴을 학습하고 새로운 데이터에 대해 예측이나 분류를 할 수 있도록 만든 수학적 또는 통계적 구조이다. 주가 예측, 이미지 분류 등과 같은 것이 모두 모델이다.
결측치 - Missing Value
단어 그대로 풀어보면 "측정 수치가 없는 것"이다. 측정치는 분석 대상으로부터 수집 또는 조사해서 가져온 값이고 결측치는 측청 수치를 가져올 수 없을 때 값을 비워 놓은 것이다. 결측치는 컴퓨터에서 보통 NULL, None, NaN, N/A, Nill 등으로 표시되는데, 프로그래밍 언어와 데이터 플랫폼에서 쉽게 볼 수 있다. 그 외에 비어 있는 값을 특별한 표시값으로 채워서 값은 있지만 결측치 인 경우도 있다.
ℹ️알아두기: 통계학에서는 표본 자료로 부터 추출한 값을 관측치(Observation)라고 한다. 관측치가 빠져 있으면 결측치라고 부른다.
결측치가 생기는 이유
- 설문 조사에서 응답을 하지 않은 경우
- 센서에서 값을 주지 못한 경우
- 시스템에서 데이터를 기록하지 못한 경우
- 데이터 결합할 때 한쪽 데이터가 충분하지 않은 경우
- 데이터 정제 후 깨진 값이 사라진 경우
이 외에도 다양한 이유로 결측치가 생길 수 있다. 결측치가 생기는 이유를 알아두면, 복원할지 무시할지 같은 처리 방식을 결정하는 데 도움이 된다. 복원이 가능하다면 복원하는 것이 좋지만, 실제로는 대부분 복원하기 어렵다. 하지만 결측치가 생긴 이유를 알면 복원이 가능하거나, 근사값으로 대체할 수도 있다. 이런 가능성을 확인하려면 데이터 제공자에게 결측치가 왜 발생했는지 물어보는 것도 좋다.
어쨌든 데이터를 분석하기 전에 결측치가 있는지 반드시 확인해야 한다. 그리고 어떻게 처리할지도 미리 결정해야 분석 과정에서 혼란을 줄일 수 있다.
결측치 예시
데이터에 결측치가 있는 예시를 보자.
❓프롬프트: 결측치 데이터의 예를 보여주세요.
고객 데이터 예시
고객ID | 이름 | 나이 | 성별 | 연봉 | 연락처 | 구매횟수 |
---|---|---|---|---|---|---|
C001 | 김철수 | 35 | 남 | 4500 | 010-1234-5678 | 12 |
C002 | 이영희 | NULL | 여 | 3800 | NULL | 8 |
C003 | 박민수 | 28 | NULL | NULL | 010-9876-5432 | 5 |
C004 | 정수진 | 42 | 여 | 5200 | 010-5555-1234 | NULL |
C005 | NULL | 31 | 남 | 4100 | NULL | 15 |
센서 데이터 예시
시간 | 온도(°C) | 습도(%) | 압력(hPa) | 풍속(m/s) |
---|---|---|---|---|
09:00 | 22.5 | 65 | 1013.2 | 2.3 |
09:10 | NaN | 67 | 1013.1 | 2.1 |
09:20 | 23.1 | NULL | NULL | 2.5 |
09:30 | 23.8 | 70 | 1012.8 | -- |
09:40 | 24.2 | 72 | 1012.5 | 3.1 |
설문조사 데이터 예시
응답자ID | Q1_만족도 | Q2_추천의향 | Q3_재구매 | Q4_나이대 | Q5_소득수준 |
---|---|---|---|---|---|
R001 | 5 | 4 | 예 | 30대 | 중간 |
R002 | 무응답 | 5 | 예 | 20대 | 무응답 |
R003 | 3 | 무응답 | 아니오 | 무응답 | 낮음 |
R004 | 4 | 3 | 무응답 | 40대 | 높음 |
R005 | 무응답 | 무응답 | 예 | 50대 | 중간 |
의료 데이터 예시
환자ID | 혈압_수축기 | 혈압_이완기 | 혈당 | BMI | 흡연여부 | 진단결과 |
---|---|---|---|---|---|---|
P001 | 120 | 80 | 95 | 23.5 | 비흡연 | 정상 |
P002 | 135 | 미측정 | 110 | NULL | 흡연 | 주의 |
P003 | 결측 | 결측 | 88 | 26.2 | 무응답 | 정상 |
P004 | 145 | 95 | 미검사 | 28.1 | 비흡연 | 고위험 |
P005 | 118 | 75 | 92 | NULL | 무응답 | 미확정 |
NULL, 무응답, 미측정, 결측, NaN 으로 표기된 것들은 모두 결측치라는 것을 눈치 챌 수 있다. “무응답”인 경우 이 값은 문자열로 값이 있지만 의미상으로 결측치이다. 결측치를 표시하는 규칙은 데이터와 시스템에 따라 다르다. 어떤 표기값이 결측치인지는 데이터와 함께 제공되는 데이터 명세를 보거나 제공자에게 물어봐야한다. 그렇지 못할 때는 논리적으로 판단해야 한다.
ℹ️알아두기: 데이터 명세(Data Description)는 데이터를 제공할 때 데이터에 대해 설명하는 문서다. CSV, Excel, PDF, 웹페이지 등으로 제공되는데. 컬럼 설명, 단위, 결측치 코드 등을 포함하고 있다. 그 외에도 참고 사항이나 예외 사항도 있어 꼭 확인해야 한다.
조금 더 많은 데이터를 보자.
이름 | 나이 | 성별 | 키(cm) | 몸무게(kg) |
---|---|---|---|---|
김철수 | 27 | 남 | 178 | 72 |
이영희 | 25 | 모름 | 165 | 54 |
박민수 | 30 | 남 | 182 | 80 |
최지우 | 29 | 여 | 170 | 공개하지 않음 |
정수진 | 26 | 공개하지 않음 | 160 | 50 |
김하늘 | 28 | 여 | 168 | 58 |
이준호 | 31 | 남 | 180 | 76 |
박서연 | 26 | 162 | 51 | |
정민수 | 27 | 남 | 175 | 공개하지 않음 |
최유진 | 25 | 공개하지 않음 | 169 | 60 |
성별 컬럼에서는 “모름”, “공개하지 않음”, 비어있는 것 중 어떤 것들을 결측치라고 정해야 한다. 예를 들어 성별에 “남”과 “여”만 있어야 한다고 결정했다면 그 외의 값은 결측치다. “모름”이나 “공개하지 않음”도 성별로 허용한다고 결정했다면 결측치가 아니다. 데이터와 분석 주제에 따라 선택이 달라지며 분석가와 분석을 의뢰한 사람이 결정한다.
결측치 처리 방법
결측치 처리 방법은 크게 두 가지다.
- 결측치가 포함된 행이나 열을 모두 제거한다
- 결측치를 모두 어떤 값으로 채운다
결측치를 처리하지 않고 그대로 남겨두고 분석을 위한 계산에서 결측치를 제외하고 계산하는 방법도 있는데 이것도 제거하는 것 같은 것이다.
ℹ️ 결측치 제거
실무에서 “결측치를 제거한다” 또는 “결측치를 없앤다”라는 표현도 많이 쓰는데 “결측치를 처리한다”를 의미하는 것이다. 그래서 결측치 제거는 빈 곳에 데이터를 채우거나 빈 곳이 많은 열이나 행을 삭제하는 것도 모두 포함하는 의미다.
현실에서는 위의 방법 중 하나를 일방적으로 선택해서 모든 경우에 적용하기는 어렵다. 주어진 데이터와 상황과 값의 항목에 따라 달라진다.
결측치 포함 행과 열을 삭제
결측치를 없애는 가장 쉬운 방법은 열이나 행을 통째로 삭제하는 것이다.
이름 | 나이 | 키(cm) | 몸무게(kg) |
---|---|---|---|
김철수 | 25 | 175 | 70 |
이영희 | 162 | 55 | |
박민수 | 30 | 68 | |
최지우 | 28 | 168 | |
정민수 | 32 | 180 | 75 |
최유진 | 26 | 165 | 58 |
김하늘 | 29 | 172 | 62 |
이준호 | 177 | 73 | |
박서연 | 24 | 49 | |
정민수 | 33 | 183 |
표에서 '나이', '키(cm)', '몸무게(kg)'에 값이 없는 것이 결측치(Missing Value)다. 위의 데이터로 연령별 몸무게를 조사하는 간단한 분석을 한다고 하자. 그러면 "나이"와 "몸무게"가 있는 컬럼(column, 열)의 값은 반드시 필요하다. 그렇다면 "이영희"의 나이는 어떻게 채워줘야 할 것인가?
사람의 나이를 추정해서 채울 수 있는 방법은 없다. 그래서 "이영희" 데이터의 행은 제거해야 한다. 마찬가지로 키가 기록되지 않은 "박민수"와 "이준호", 몸무게가 없는 "최지우", "박서연", "정민수"의 데이터도 행을 통째로 삭제한다.
📊 코드 예제
import pandas as pd
import numpy as np
# 예제 데이터 생성
# np.nan은 pandas에서 결측치를 나타내는 값이다
data = {
'이름': ['김철수', '이영희', '박민수', '최지우', '정민수',
'최유진', '김하늘', '이준호', '박서연', '정민수'],
'나이': [25, np.nan, 30, 28, 32, 26, 29, np.nan, 24, 33],
'키(cm)': [175, 162, np.nan, 168, 180, 165, 172, 177, np.nan, 183],
'몸무게(kg)': [70, 55, 68, np.nan, 75, 58, 62, 73, 49, np.nan]
}
# 딕셔너리를 DataFrame으로 변환한다
df = pd.DataFrame(data)
print("원본 데이터:")
print(df)
# 결측치가 있는 행 삭제
# dropna() 메서드는 기본적으로 하나라도 결측치가 있는 행을 모두 삭제한다
df_dropna = df.dropna()
print("\n결측치가 있는 행 삭제 후:")
print(df_dropna)
# 결측치가 있는 열 삭제 (모든 행에서 결측치가 없는 열만 남김)
# axis=1 옵션을 사용하면 열(컬럼) 단위로 삭제한다
# 하나라도 결측치가 있는 열은 모두 삭제된다
df_dropna_cols = df.dropna(axis=1)
print("\n결측치가 있는 열 삭제 후:")
print(df_dropna_cols)
# 결측치가 있는 행과 열을 모두 삭제
df_clean = df.dropna().dropna(axis=1)
print("\n결측치가 있는 행과 열 삭제 후:")
print(df_clean)
💻 실행 결과
원본 데이터:
이름 나이 키(cm) 몸무게(kg)
0 김철수 25.0 175.0 70.0
1 이영희 NaN 162.0 55.0
2 박민수 30.0 NaN 68.0
3 최지우 28.0 168.0 NaN
4 정민수 32.0 180.0 75.0
5 최유진 26.0 165.0 58.0
6 김하늘 29.0 172.0 62.0
7 이준호 NaN 177.0 73.0
8 박서연 24.0 NaN 49.0
9 정민수 33.0 183.0 NaN
결측치가 있는 행 삭제 후:
이름 나이 키(cm) 몸무게(kg)
0 김철수 25.0 175.0 70.0
4 정민수 32.0 180.0 75.0
5 최유진 26.0 165.0 58.0
6 김하늘 29.0 172.0 62.0
결측치가 있는 열 삭제 후:
이름
0 김철수
1 이영희
2 박민수
3 최지우
4 정민수
5 최유진
6 김하늘
7 이준호
8 박서연
9 정민수
결측치가 있는 행과 열 삭제 후:
이름 나이 키(cm) 몸무게(kg)
0 김철수 25.0 175.0 70.0
4 정민수 32.0 180.0 75.0
5 최유진 26.0 165.0 58.0
6 김하늘 29.0 172.0 62.0
마지막 결과가 행과 열에 결측치가 포함된 것을 모두 삭제한 것이다. 하지만 이 결과에서는 많은 분량의 데이터가 삭제되었기 때문에 분석을 제대로 못할 가능성이 크다. 그래서 실무에서는 결측치가 아주 많지 않은 경우 행이나 열을 그대로 두고 계산이 필요할 때만 제외하는 경우가 많다.
위의 실행결과에서도 원래 10개의 행이었던 데이터가 결측치가 있는 행을 모두 제거한 후 4개 밖에 남지 않았다. 전체의 40%만 남게 되었고 제거된 60%를 무시하고 소수가 전체를 대변하기 어렵다. 이런 상태로 분석하게 되면 잘못 해석하게 된다. 그래서 애초에 데이터가 수집, 전송, 변형되는 단계에서 결측치가 발생하지 않게 최대한 신경써야 한다.
결측치 비율 확인하기
결측치가 얼마나 되는지는 항상 확인해야하며 기록도 해야 한다.
주어진 데이터로 결측치가 어느정도인지 확인하는 코드를 작성해 주세요.
# 결측치 비율 확인
print("=== 결측치 현황 분석 ===")
# 1. 전체 결측치 개수와 비율
total_cells = df.size # 전체 셀 개수
missing_cells = df.isna().sum().sum() # 전체 결측치 개수
missing_ratio = (missing_cells / total_cells) * 100
print(f"전체 데이터 셀 개수: {total_cells}")
print(f"전체 결측치 개수: {missing_cells}")
print(f"전체 결측치 비율: {missing_ratio:.1f}%")
# 2. 컬럼별 결측치 현황
print("\n컬럼별 결측치 현황:")
for col in df.columns:
if df[col].dtype != 'object': # 숫자형 컬럼만 확인
missing_count = df[col].isna().sum()
total_count = len(df[col])
missing_pct = (missing_count / total_count) * 100
print(f"{col}: {missing_count}/{total_count} ({missing_pct:.1f}%)")
# 3. 행별 결측치 현황
print("\n행별 결측치 현황:")
row_missing = df.isna().sum(axis=1)
for idx, count in enumerate(row_missing):
student_name = df.iloc[idx]['이름'] if '이름' in df.columns else f"행 {idx}"
if count > 0:
print(f"{student_name}: {count}개 결측")
# 4. 완전한 데이터 비율
complete_rows = df.dropna().shape[0]
total_rows = df.shape[0]
complete_ratio = (complete_rows / total_rows) * 100
print(f"\n완전한 데이터 행: {complete_rows}/{total_rows} ({complete_ratio:.1f}%)")
print(f"결측치가 있는 행: {total_rows - complete_rows}/{total_rows} ({100 - complete_ratio:.1f}%)")
=== 결측치 현황 분석 ===
전체 데이터 셀 개수: 40
전체 결측치 개수: 6
전체 결측치 비율: 15.0%
컬럼별 결측치 현황:
나이: 2/10 (20.0%)
키(cm): 2/10 (20.0%)
몸무게(kg): 2/10 (20.0%)
행별 결측치 현황:
이영희: 1개 결측
박민수: 1개 결측
최지우: 1개 결측
이준호: 1개 결측
박서연: 1개 결측
정민수: 1개 결측
완전한 데이터 행: 4/10 (40.0%)
결측치가 있는 행: 6/10 (60.0%)
결측치 채우기 - Missing Value Filling
결측치를 채우는 것을 더 해보자.
학생명 | 국어 | 영어 | 수학 |
---|---|---|---|
김민준 | 90 | 85 | |
이서연 | 88 | 92 | |
박지후 | 78 | 80 | |
최예린 | 85 | 90 | 87 |
이 데이터는 어떤 스터디 그룹의 성적이다. "이 스터디그룹의 과목별 실력은 대략 어떠한가?"라는 질문에 대해 데이터 탐색을 해보자. 먼저 데이터에 '국어', '영어', '수학' 점수에 결측치가있다. 앞에서 처럼 결측치가 있는 행을 모두 제거한다면 "최예린" 행만 남게되어 대부분 데이터가 사라진다. 그래서 행 전체를 삭제하지 않고 빈 곳을 채운다. 비곳을 채우는 방법 중 가장 쉬운 것은 0으로 모두 채우는 것이다. 하지만 0으로 채우면 평균을 계산했을때 전체 학생의 평균이 떨어지게 된다. 결측치가 있는 학생의 과모 별 실력이 0점일 정도라 낮지 않을 것이다. 그래서 이런 경우는 결측치를 학생 값의 평균이나 중위수값을 준다. 위에서 “이서연”의 “국어” 성적은 “국어” 열 전체의 평균으로 넣는다.
📊 코드 예제
import pandas as pd
import numpy as np
# 학생 성적 데이터 생성
data = {
'학생명': ['김민준', '이서연', '박지후', '최예린'],
'국어': [90, np.nan, 78, 85],
'영어': [85, 88, np.nan, 90],
'수학': [np.nan, 92, 80, 87]
}
# DataFrame 생성
df = pd.DataFrame(data)
print("원본 데이터:")
print(df)
# 결측치 확인
print("\n결측치 확인:")
print(df.isna().sum())
# 방법 1: 각 과목별 평균으로 결측치 채우기
df_mean = df.copy()
df_mean.iloc[:, 1:] = df_mean.iloc[:, 1:].fillna(df_mean.iloc[:, 1:].mean())
print("\n평균값으로 결측치 채운 데이터:")
print(df_mean)
# 방법 2: 각 과목별 중앙값으로 결측치 채우기
df_median = df.copy()
df_median.iloc[:, 1:] = df_median.iloc[:, 1:].fillna(df_median.iloc[:, 1:].median())
print("\n중앙값으로 결측치 채운 데이터:")
print(df_median)
💻 실행 결과
원본 데이터:
학생명 국어 영어 수학
0 김민준 90.0 85.0 NaN
1 이서연 NaN 88.0 92.0
2 박지후 78.0 NaN 80.0
3 최예린 85.0 90.0 87.0
결측치 확인:
학생명 0
국어 1
영어 1
수학 1
dtype: int64
평균값으로 결측치 채운 데이터:
학생명 국어 영어 수학
0 김민준 90.000000 85.000000 86.333333
1 이서연 84.333333 88.000000 92.000000
2 박지후 78.000000 87.666667 80.000000
3 최예린 85.000000 90.000000 87.000000
중앙값으로 결측치 채운 데이터:
학생명 국어 영어 수학
0 김민준 90.0 85.0 87.0
1 이서연 85.0 88.0 92.0
2 박지후 78.0 88.0 80.0
3 최예린 85.0 90.0 87.0
Pandas를 사용하면 결측치를 평균값, 중앙값 등으로 채워는 작업을 쉽게 할 수 있다.
ℹ️ 결측치 제거를 쉡게 처리해주는 라이브러리가 없는 언어나 도구는 직접 개발하거나 손으로 해야 하므로 매우.
예제를 하나 더 보자. 날마다 온도와 습도를 측정하는 장치에서 기록한 값이 있다.
날짜 | 온도(℃) | 습도(%) |
---|---|---|
2025-06-01 | 23.5 | 60 |
2025-06-02 | 58 | |
2025-06-03 | 25.1 | |
2025-06-04 | 24.8 | 62 |
2025-06-05 |
값이 빈 곳은 모두 결측치다. 이 데이터는 자연 데이터이기 때문에 채워서 보정을 해도 무리가 없다. 하지만 온도나 습도의 빈 값을 채우기 위해서 평균값이나 중위수값을 넣는다면 분석에 논리적 문제가 생길 수 있다.
만약 위의 데이터가 1년분이 있다고 하면, 한국은 1년동안온 온도와 습도의 변하가 심하기 때문에 전체 평균을 주면 왜곡이 생긴다. 채워 넣은 데이터가 분석을 하는데 방해되지 않도록 평범한 값으로 넣어야 한다. 이런 경우 결측치는 전날과 다음날의 평균값을 넣는 방법을 사용한다. 2025-06-02의 온도는 23.5와 25.1의 평균값을 넣는다. 2025-06-05일의 온도는 전날 데이터는 있지만 다음날 데이터가 없으므로 그대로 전날의 값을 넣어서 24.8로 한다. 습도도 같은 방법을 사용할 수 있다.
채워넣은 후는 다음과 같이 된다.
날짜 | 온도(℃) | 습도(%) |
---|---|---|
2025-06-01 | 23.5 | 60 |
2025-06-02 | 24.3 | 58 |
2025-06-03 | 25.1 | 60 |
2025-06-04 | 24.8 | 62 |
2025-06-05 | 24.8 | 62 |
위와 같은 방식으로 결측지를 제거하는 코드를 보자.
import pandas as pd
import numpy as np
# 온도 및 습도 데이터 생성
data = {
'날짜': ['2025-06-01', '2025-06-02', '2025-06-03', '2025-06-04', '2025-06-05'],
'온도(℃)': [23.5, np.nan, 25.1, 24.8, np.nan],
'습도(%)': [60, 58, np.nan, 62, np.nan]
}
# DataFrame 생성
df = pd.DataFrame(data)
# 날짜 열을 날짜 형식으로 변환
df['날짜'] = pd.to_datetime(df['날짜'])
print("원본 데이터:")
print(df)
# 결측치 확인
print("\n결측치 확인:")
print(df.isna().sum())
# 방법 1: 전날과 다음날의 평균값으로 결측치 채우기 (앞선 설명대로 구현)
df_filled = df.copy()
# 보간(interpolation) 메서드를 사용하여 앞뒤 데이터의 평균으로 채우기
df_filled_interpolate = df.copy()
df_filled_interpolate[['온도(℃)', '습도(%)']] = df_filled_interpolate[['온도(℃)', '습도(%)']].interpolate(method='linear')
# 마지막 행의 결측치는 전날 값으로 채우기 (forward fill)
df_filled_interpolate = df_filled_interpolate.fillna(method='ffill')
print("\n앞뒤 데이터 평균과 전날 값으로 결측치 채운 데이터:")
print(df_filled_interpolate)
# 방법 2: 수동으로 직접 값 계산하여 채우기 (앞선 설명과 동일한 로직)
df_manual = df.copy()
# 2025-06-02 온도 채우기: (23.5 + 25.1) / 2 = 24.3
df_manual.loc[1, '온도(℃)'] = (df_manual.loc[0, '온도(℃)'] + df_manual.loc[2, '온도(℃)']) / 2
# 2025-06-03 습도 채우기: (58 + 62) / 2 = 60
df_manual.loc[2, '습도(%)'] = (df_manual.loc[1, '습도(%)'] + df_manual.loc[3, '습도(%)']) / 2
# 2025-06-05 온도와 습도 채우기: 전날 값 사용
df_manual.loc[4, '온도(℃)'] = df_manual.loc[3, '온도(℃)'] # 24.8
df_manual.loc[4, '습도(%)'] = df_manual.loc[3, '습도(%)'] # 62
print("\n직접 계산하여 결측치를 채운 데이터:")
print(df_manual)
# 결과 검증 - 두 방법의 결과가 같은지 확인
print("\n두 방법의 결과 비교 (거의 같아야 함):")
print(df_manual[['온도(℃)', '습도(%)']] - df_filled_interpolate[['온도(℃)', '습도(%)']])
💻 실행 결과
원본 데이터:
날짜 온도(℃) 습도(%)
0 2025-06-01 23.5 60
1 2025-06-02 NaN 58
2 2025-06-03 25.1 NaN
3 2025-06-04 24.8 62
4 2025-06-05 NaN NaN
결측치 확인:
날짜 0
온도(℃) 2
습도(%) 2
dtype: int64
앞뒤 데이터 평균과 전날 값으로 결측치 채운 데이터:
날짜 온도(℃) 습도(%)
0 2025-06-01 23.5 60.0
1 2025-06-02 24.3 58.0
2 2025-06-03 25.1 60.0
3 2025-06-04 24.8 62.0
4 2025-06-05 24.8 62.0
직접 계산하여 결측치를 채운 데이터:
날짜 온도(℃) 습도(%)
0 2025-06-01 23.5 60.0
1 2025-06-02 24.3 58.0
2 2025-06-03 25.1 60.0
3 2025-06-04 24.8 62.0
4 2025-06-05 24.8 62.0
두 방법의 결과 비교 (거의 같아야 함):
온도(℃) 습도(%)
0 0.0 0.0
1 0.0 0.0
2 0.0 0.0
3 0.0 0.0
4 0.0 0.0
위의 코드는 Pandas의 interpolate() 메서드와 fillna()기능을 이용한 방법과 직접 작성한 방법 두 가지를 쓴다. 두 방법 모두 동일한 결과를 얻을 수 있다. 다른 경우에도 Pandas에서 기능을 제공한다면 그대로 가져다 쓰면 되지만 그렇지 않은 경우도 많음. 직접 작성해야 한다. 앞서 설명한 대로 중간의 결측치는 전날과 다음날의 평균값을, 마지막 행의 결측치는 전날 값을 사용하여 채웠다.
이 외에도 결측치를 제거하는 방법은 많고 직접 방법을 만들어 써도 된다.
❓프롬프트: 결측치를 제거하는 방법에 대해서 모두 알려주세요
결측치 제거 방법
결측치 제거 방법들을 다시 정리한다.
행 또는 열 삭제 (제거)
- 결측치가 포함된 행(row)이나 열(column)을 통째로 삭제하는 방법이다.
- 데이터 손실이 크지 않거나 결측치가 적을 때 주로 사용한다.
- Pandas에서는
dropna()
함수를 사용한다.
📊 예시 코드
# 결측치가 있는 행 삭제
df_clean = df.dropna()
# 결측치가 있는 열 삭제
df_clean_col = df.dropna(axis=1)
특정 기준에 따라 삭제
결측치가 일정 개수 이상인 행/열만 삭제하거나, 특정 열에 결측치가 있을 때만 삭제하는 방법이다.
📊 예시 코드
# 결측치가 2개 이상인 행만 삭제
df_clean = df.dropna(thresh=df.shape[1]-1)
# '온도(℃)' 컬럼에 결측치가 있는 행만 삭제
df_clean = df.dropna(subset=['온도(℃)'])
결측치 대체(채우기, 보간)
- 결측치를 평균, 중앙값, 최빈값, 이전/다음 값, 특정 값 등으로 대체하는 방법이다.
- 데이터 손실을 막을 수 있다.
- Pandas에서는
fillna()
,interpolate()
등을 사용한다.
비교적 많이 쓰이는 방식이다.
📊 예시 코드
# 평균값으로 채우기
df_filled = df.fillna(df.mean(numeric_only=True))
# 이전 값으로 채우기 (forward fill)
df_filled = df.fillna(method='ffill')
# 다음 값으로 채우기 (backward fill)
df_filled = df.fillna(method='bfill')
# 보간법(interpolation)으로 채우기
df_filled = df.interpolate()
모델 기반 대체
- 결측치가 있는 데이터를 예측 모델(회귀, 분류 등)을 사용해 채우는 방법이다.
- 예를 들어, 다른 변수와의 관계를 이용해 결측값을 예측할 수 있다.
- scikit-learn의
IterativeImputer
등 다양한 기법이 있다.
이 방법은 처리 속도가 느려 잘 쓰지 않는 방식이지만 알아둘 필요가 있다.
특정 값(예: 0, -1, 'Unknown' 등)으로 채우기
결측치가 의미 있는 값(예: 0, '없음', '미상')으로 대체될 수 있을 때 사용한다.
가장 쉽고 빠른 방법이자만 가장 조심해야 하는 방식이다.
📊 예시 코드
df_filled = df.fillna(0)
df_filled = df.fillna('Unknown')
그룹별 대체
- 그룹(예: 지역, 성별, 연령대 등)별로 평균, 중앙값 등으로 결측치를 채우는 방법이다.
- 같은 그룹 내의 특성을 반영하여 더 정확한 대체값을 사용할 수 있다.
결측치 처리 정리
- 결측치가 적으면 삭제, 많으면 대체(채우기) 방법을 주로 사용한다.
- 데이터의 특성과 분석 목적에 따라 적절한 방법을 선택해야 한다.
- 여러 방법을 조합해서 사용할 수도 있다.
결측치 처리 방법 요약
방법 | 사용 예 | 장점 | 주의점 |
---|---|---|---|
삭제(dropna) | 결측치가 적은 경우 | 간단하고 빠름 | 데이터 손실 |
평균 채우기 | 수치형 변수 | 쉬움 | 왜곡 위험 |
그룹별 평균 | 동일 집단 내 대체 | 정밀함 | 그룹 적절성 중요 |